using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using Microsoft.SqlServer.Dts.Runtime;
using Microsoft.SqlServer.Dts.Pipeline.Wrapper;
using System.Collections;
using System.Reflection;
using Microsoft.SharedSource.SqlServer.MDDE.Repository;
using Microsoft.SharedSource.SqlServer.MDDE.Components;
using System.Text.RegularExpressions;

namespace Microsoft.SharedSource.SqlServer.MDDE.Middleware
{
    public class MDDEGeneralEventHandler : DefaultEvents
    {
        public override bool OnError(DtsObject source, int errorCode, string subComponent, string description, string helpFile, int helpContext, string idofInterfaceWithError)
        {
            return base.OnError(source, errorCode, subComponent, description, helpFile, helpContext, idofInterfaceWithError);
        }

        public override void OnWarning(DtsObject source, int warningCode, string subComponent, string description, string helpFile, int helpContext, string idofInterfaceWithError)
        {
            base.OnWarning(source, warningCode, subComponent, description, helpFile, helpContext, idofInterfaceWithError);
        }
    }

    public static partial class MDDEManager
    {
        #region Public methods

        /// <summary>
        /// Parses the input package body and returns the 
        /// tree representation of an SSIS package.
        /// </summary>
        /// <param name="packageBody"></param>
        /// <returns>The tree representation of SSIS package, null if any errors occur.</returns>
        public static XmlDocument GetPackageTree(string packageBody)
        {
            Package sourcePackage = new Package();

            try
            {
                sourcePackage.LoadFromXML(packageBody, new MDDEGeneralEventHandler());

                return GetPackageTree(sourcePackage);
            }
            catch
            {
                return null;
            }
        }

        /// <summary>
        /// This will pull the package from repository and generate the tree for same.
        /// </summary>
        /// <param name="repositoryConnectionString"></param>
        /// <param name="packageName"></param>
        /// <param name="packageVersion"></param>
        /// <returns>The tree representation of SSIS package, null if any errors occur.</returns>
        public static XmlDocument GetPackageTree(string repositoryConnectionString,
            string templateName, string packageName, string packageVersion)
        {
            try
            {
                MDDERepository repository = new MDDERepository(repositoryConnectionString);

                Package sourcePackage = new Package();
                sourcePackage.LoadFromXML(
                    repository.PackageTemplates[templateName].Packages[packageName].Body, null);

                XmlDocument packageTree = GetPackageTree(sourcePackage);

                // Add the template information.
                XmlNode xnodePackage = packageTree.SelectSingleNode("//Node[@Type=\"Package\"]");
                if (xnodePackage != null)
                {
                    AppendAttribute(xnodePackage,
                        new string[] { "Template", repository.PackageTemplates[templateName].
                            Packages[packageName].ParentTemplate.Name });

                    //XmlAttribute xattribTemplate = packageTree.CreateAttribute("Template");
                    //xattribTemplate.Value = repository.RepositoryPackages[packageName].ParentTemplate.Name;
                    //xnodePackage.Attributes.Append(xattribTemplate);
                }
                return packageTree;
            }
            catch
            {
                return null;
            }
        }

        /// <summary>
        /// This will pull the template from repository and generate the tree for same.
        /// </summary>
        /// <param name="repositoryConnectionString"></param>
        /// <param name="templateName"></param>
        /// <returns>The tree representation of SSIS package, null if any errors occur.</returns>
        public static OperationResult GetTemplateTree(
            string repositoryConnectionString, string templateName)
        {
            OperationResult result = OperationResult.Empty;
            try
            {
                // Create a repository object.
                MDDERepository repository = new MDDERepository(repositoryConnectionString);

                // Create a package template.
                MDDEPackageTemplate template = new MDDEPackageTemplate(repository, templateName);

                // Get the package tree for the template.
                XmlDocument templateTree = GetPackageTree(template.Body);
                if (templateTree != null)
                {
                    // Fill in the template tree into the results.
                    result.OperationOutputParameters.Add(templateTree);

                    // Enter success status into the log.
                    result.SuccessStatus = true;
                    result.OperationLog.Add("Success");
                }
                else
                {
                    // Enter failure status into the log.
                    result.SuccessStatus = false;
                    result.OperationLog.Add("Failure");
                }
            }
            catch (Exception ex)
            {
                result.SuccessStatus = false;
                result.OperationLog.Add(ex.Message);
            }
            return result;
        }

        /// <summary>
        /// This method creates a tree structure for the MDDE repository.
        /// </summary>
        /// <param name="repositoryConnectionString"></param>
        /// <returns>XmlDocument representing the MDDE repository.</returns>
        public static XmlDocument GetRepositoryTree(string repositoryConnectionString)
        {
            try
            {
                MDDERepository repository = new MDDERepository(repositoryConnectionString);

                XmlDocument xmlRepository = new XmlDocument();

                // Add the Repository node.
                XmlElement xmlRoot = AppendChildElement(xmlRepository, "Node",
                    new string[] { "Type","Repository","Name",repository.RepositoryIdentifier,
                        "Uri","Repository=" + repository.RepositoryIdentifier,
                        "ConnectionString",repository.RepositoryConnection.ConnectionString});

                // Create the 'Templates' node.
                XmlElement xmlTemplates =
                    AppendChildElement(xmlRoot, "Node", new string[] { "Type", "Templates", 
                        "Name", "Templates", "Uri", xmlRoot.Attributes["Uri"].Value });

                // Add in the repository templates.
                foreach (MDDEPackageTemplate template in repository.PackageTemplates)
                {
                    // Create the template node.
                    XmlElement xmlTemplate = AppendChildElement(xmlTemplates, "Node",
                        new string[] { "Type", "Template", "Name", template.Name, "Uri", 
                            xmlTemplates.Attributes["Uri"].Value + "/Template=" + template.Name });

                    // Create the 'Template Instances' node.
                    XmlElement xmlInstances = AppendChildElement(xmlTemplate, "Node",
                        new string[] { "Type", "TemplateInstances", 
                            "Name", "Template Instances", "Uri", 
                            xmlTemplate.Attributes["Uri"].Value});

                    // Add in the template instances.
                    foreach (MDDETemplateInstance tin in template.TemplateInstances)
                        AppendChildElement(xmlInstances, "Node",
                            new string[] { "Type", "Instance", "Name", tin.Name, "Uri", 
                                xmlInstances.Attributes["Uri"].Value + 
                                "/TemplateInstance=" + tin.Name});

                    // Create the 'Packages' node.
                    XmlElement xmlPackages = AppendChildElement(xmlTemplate, "Node",
                        new string[] { "Type", "Packages", "Name", "Packages", 
                        "Uri", xmlTemplate.Attributes["Uri"].Value });

                    // Add in the repository packages.
                    foreach (MDDEPackage package in template.Packages)
                    {
                        AppendChildElement(xmlPackages, "Node",
                        new string[] {  "Type", "Package", "Name", package.Name, "Uri", 
                            xmlPackages.Attributes["Uri"].Value + "/Package=" + package.Name});

                    }
                }

                return xmlRepository;
            }
            catch
            {
                return null;
            }
        }

        /// <summary>
        /// Returns the repository tree for a selected node.
        /// </summary>
        /// <param name="repositoryConnectionString"></param>
        /// <param name="selectedNode"></param>
        /// <returns></returns>
        public static XmlDocument GetRepositoryTreeForSelectedNode(string repositoryConnectionString,
            XmlNode selectedNode)
        {
            try
            {
                MDDERepository repository =
                    new MDDERepository(repositoryConnectionString);
                string selectedNodeType = selectedNode.Attributes["Type"].Value;
                string selectedNodeName = selectedNode.Attributes["Name"].Value;
                string Uri = selectedNode.Attributes["Uri"].Value;

                XmlDocument xmlRepository = new XmlDocument();

                if (selectedNodeType == "Repository")
                {
                    return GetRepositoryTree(repositoryConnectionString);
                }
                else
                {
                    // Add the node specified by the 'Type' attribute.
                    XmlElement xmlRoot = AppendChildElement(xmlRepository, "Node", new string[] { 
                        "Type", selectedNodeType, "Name", selectedNodeName, "Uri", Uri });

                    if (selectedNodeType == "Templates")
                    {
                        // Add in the repository templates.
                        foreach (MDDEPackageTemplate template in repository.PackageTemplates)
                        {
                            // Create the template node.
                            XmlElement xmlTemplate = AppendChildElement(xmlRoot, "Node",
                                new string[] {"Type", "Template", "Name", template.Name, "Uri", 
                                xmlRoot.Attributes["Uri"].Value + "/Template=" + template.Name});

                            // Create the 'Template Instances' node.
                            XmlElement xmlInstances =
                                AppendChildElement(xmlTemplate, "Node",
                                new string[] {"Type", "TemplateInstances", 
                                "Name", "Template Instances", "Uri", 
                                xmlTemplate.Attributes["Uri"].Value});

                            // Add in the template instances.
                            foreach (MDDETemplateInstance tin in template.TemplateInstances)
                                AppendChildElement(xmlInstances, "Node",
                                new string[] {"Type", "Instance", "Name", tin.Name, "Uri", 
                                xmlInstances.Attributes["Uri"].Value + 
                                "/TemplateInstance=" + tin.Name});

                            // Create the 'Packages' node.
                            XmlElement xmlPackages = AppendChildElement(xmlTemplate, "Node",
                                new string[] { "Type", "Packages", "Name", "Packages", 
                                "Uri", xmlTemplate.Attributes["Uri"].Value });

                            // Add in the repository packages.
                            foreach (MDDEPackage package in template.Packages)
                            {
                                AppendChildElement(xmlPackages, "Node",
                                new string[] {  "Type", "Package", "Name", package.Name, "Uri", 
                                    xmlPackages.Attributes["Uri"].Value + "/Package=" + package.Name});

                            }
                        }
                    }
                    else if (selectedNodeType == "Template")
                    {
                        // Create the 'Template Instances' node.
                        XmlElement xmlInstances =
                            AppendChildElement(xmlRoot, "Node",
                            new string[] {"Type", "TemplateInstances", 
                            "Name", "Template Instances", "Uri", 
                            xmlRoot.Attributes["Uri"].Value});

                        // Add in the template instances.

                        if (repository.PackageTemplates.ContainsTemplate(selectedNodeName))
                        {
                            foreach (MDDETemplateInstance tin in
                                repository.PackageTemplates[selectedNodeName].TemplateInstances)
                                AppendChildElement(xmlInstances, "Node",
                                    new string[] {"Type", "Instance", "Name", tin.Name, "Uri", 
                                    xmlInstances.Attributes["Uri"].Value + 
                                    "/TemplateInstance=" + tin.Name});

                            // Create the 'Packages' node.
                            XmlElement xmlPackages = AppendChildElement(xmlRoot, "Node",
                                new string[] { "Type", "Packages", "Name", "Packages", 
                                "Uri", xmlRoot.Attributes["Uri"].Value });

                            // Add in the repository packages.
                            foreach (MDDEPackage package in repository.PackageTemplates[selectedNodeName].Packages)
                            {
                                AppendChildElement(xmlPackages, "Node",
                                new string[] {  "Type", "Package", "Name", package.Name, "Uri", 
                                    xmlPackages.Attributes["Uri"].Value + "/Package=" + package.Name});

                            }
                        }
                    }

                    if (selectedNodeType == "TemplateInstances")
                    {
                        string[] s = Regex.Split(Uri, "Template=");
                        string[] s1 = Regex.Split(s[1], "/");

                        if (repository.PackageTemplates.ContainsTemplate(s1[0]))
                        {
                            foreach (MDDETemplateInstance tin in
                                repository.PackageTemplates[s1[0]].TemplateInstances)
                                AppendChildElement(xmlRoot, "Node",
                                    new string[] {"Type", "Instance", "Name", tin.Name, "Uri", 
                                        xmlRoot.Attributes["Uri"].Value + 
                                        "/TemplateInstance=" + tin.Name});
                        }

                    }
                    if ((selectedNodeType == "Packages"))
                    {
                        string[] s = Regex.Split(Uri, "Template=");
                        string[] s1 = Regex.Split(s[1], "/");

                        if (repository.PackageTemplates.ContainsTemplate(s1[0]))
                        {
                            foreach (MDDEPackage package in repository.PackageTemplates[s1[0]].Packages)
                            {
                                if (s1[0] == package.ParentTemplate.Name)
                                    AppendChildElement(xmlRoot, "Node",
                                    new string[] {  "Type", "Package", "Name", package.Name, "Uri", 
                        xmlRoot.Attributes["Uri"].Value + "/Package=" + package.Name});
                            }
                        }
                    }
                }

                return xmlRepository;
            }
            catch
            {
                return null;
            }
        }

        /// <summary>
        /// Returns a list of template instances in the repository.
        /// </summary>
        /// <param name="repositoryConnectionString"></param>
        /// <param name="templateName"></param>
        /// <returns></returns>
        public static XmlDocument GetTemplateInstances(
            string repositoryConnectionString, string templateName)
        {
            try
            {
                // Create repository object.
                MDDERepository repository = new MDDERepository(repositoryConnectionString);

                // Get the template object.
                MDDEPackageTemplate template = repository.PackageTemplates[templateName];

                // Create the xml document to be returned.
                XmlDocument xmlTins = new XmlDocument();

                XmlElement rootElement = AppendChildElement(xmlTins, "TemplateInstances", null);
                //XmlElement rootElement = xmlTins.CreateElement("TemplateInstances");
                //xmlTins.AppendChild(rootElement);

                foreach (MDDETemplateInstance tin in template.TemplateInstances)
                {
                    AppendChildElement(rootElement, "TemplateInstance",
                        new string[] { "Name", tin.Name });
                    //XmlElement xmlTin =
                    //    xmlTins.CreateElement("TemplateInstance");
                    //rootElement.AppendChild(xmlTin);

                    //XmlAttribute xmlTemplateName =
                    //    xmlTins.CreateAttribute("Name");
                    //xmlTemplateName.Value = tin.Name;
                    //xmlTin.Attributes.Append(xmlTemplateName);
                }

                return xmlTins;
            }
            catch
            {
                return null;
            }
        }

        /// <summary>
        /// Returns a list of packages dependent on a template.
        /// </summary>
        /// <param name="repositoryConnectionString"></param>
        /// <param name="templateName"></param>
        /// <returns></returns>
        public static XmlDocument GetPackages(
            string repositoryConnectionString, string templateName)
        {
            try
            {
                // Create repository object.
                MDDERepository repository = new MDDERepository(repositoryConnectionString);

                // Get the template object.
                MDDEPackageTemplate template = repository.PackageTemplates[templateName];

                // Create the xml document to be returned.
                XmlDocument xmlPackages = new XmlDocument();
                XmlElement rootElement = AppendChildElement(xmlPackages, "Packages", null);

                //XmlElement rootElement =
                //    xmlPackages.CreateElement("Packages");
                //xmlPackages.AppendChild(rootElement);
                foreach (MDDEPackage package in template.Packages)
                {
                    AppendChildElement(rootElement, "Package",
                        new string[] { "Name", package.Name });
                    //XmlElement xmlPackage =
                    //    xmlPackages.CreateElement("Package");
                    //rootElement.AppendChild(xmlPackage);

                    //XmlAttribute xmlTemplateName =
                    //    xmlPackages.CreateAttribute("Name");
                    //xmlTemplateName.Value = package.Name;
                    //xmlPackage.Attributes.Append(xmlTemplateName);
                }

                return xmlPackages;
            }
            catch
            {
                return null;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="repositoryConnectionString"></param>
        /// <returns></returns>
        public static List<string> GetTemplateList(string repositoryConnectionString)
        {
            try
            {
                List<string> templates = new List<string>();

                // Create repository object.
                MDDERepository repository = new MDDERepository(repositoryConnectionString);

                foreach (MDDEPackageTemplate template in repository.PackageTemplates)
                {
                    templates.Add(template.Name);
                }

                return templates;
            }
            catch
            {
                return null;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="repositoryConnectionString"></param>
        /// <param name="packageName"></param>
        /// <param name="packageVersion"></param>
        /// <returns></returns>
        public static XmlDocument GetPackage(string repositoryConnectionString,
            string packageName, string templateName, string packageVersion)
        {
            MDDERepository repository = new MDDERepository(repositoryConnectionString);

            XmlDocument packageBody = new XmlDocument();
            packageBody.LoadXml(repository.
                PackageTemplates[templateName].Packages[packageName].Body);
            return packageBody;
        }

        #endregion

        #region Private helper methods

        private static XmlDocument GetPackageTree(Package package)
        {
            // Create an execution DAG for the package.
            MDDEExecutionDAG packageDag =
                new MDDEExecutionDAG(package);

            // Create a tree document.
            XmlDocument packageTree = packageDag.GetComponentTree();

            return packageTree;
        }

        #region Changes xml generation methods.

        private static void AddTemplateComponentsToChangesXml(
            XmlDocument changesXml, XmlDocument templateConfigurations, XmlDocument packageTree)
        {
            try
            {
                // Iterate through all the configurable components.
                foreach (XmlNode templateComponent in
                    templateConfigurations.SelectNodes("//ConfigurableComponent"))
                {
                    // Pick out the URI.
                    string componentUri = templateComponent.Attributes["ComponentUri"].Value;

                    // Try locating the component in changes xml.
                    XmlNode changesXmlComponent =
                        changesXml.SelectSingleNode("//*[@URL=\"" + componentUri + "\"]");

                    // Try locating the component in package tree.
                    XmlNode packageTreeComponent =
                        packageTree.SelectSingleNode("//*[@Uri=\"" + componentUri + "\"]");

                    // Create a new component node if not already present.
                    if (changesXmlComponent == null)
                    {
                        if (packageTreeComponent != null)
                        {
                            changesXmlComponent =
                                GetChangesXmlNodeFromPackageNode(packageTreeComponent, templateComponent);

                            // Import the node into the changesXml.
                            if (changesXmlComponent != null)
                                changesXml.FirstChild.AppendChild(
                                    changesXml.ImportNode(changesXmlComponent, true));
                        }
                    }
                    // If the component is already present, 
                    // drill down to find out non-visited properties, mappings and collections.
                    else
                    {
                        // Get the key attributes from the component.
                        string componentName = changesXmlComponent.Attributes["Name"].Value;
                        string componentType = changesXmlComponent.Name;

                        // Add template scalar properties.
                        AddScalarProperties(packageTreeComponent, templateComponent, changesXmlComponent,
                            componentName, componentType, componentUri,
                            (XmlElement)changesXml.FirstChild);

                        // Add Mappings.
                        AddMappings(packageTreeComponent, templateComponent, changesXmlComponent,
                            componentName, componentType, componentUri,
                            (XmlElement)changesXml.FirstChild);

                        AddCollections(packageTreeComponent, templateComponent,
                            (XmlElement)changesXmlComponent);
                    }
                }

            }
            catch
            {
                // Gobble up all exceptions.
            }
        }

        private static XmlNode GetChangesXmlNodeFromPackageNode(XmlNode packageTreeComponent, XmlNode templateComponent)
        {
            // TODO: Remove this method and move its functionality into its caller.

            string componentName, componentType, componentUri;

            try
            {
                // Try getting key attributes from the component.
                componentName = packageTreeComponent.Attributes["Name"].Value;
                componentType = packageTreeComponent.Attributes["Type"].Value;
                componentUri = packageTreeComponent.Attributes["Uri"].Value;

                // There is difference in the component types of package tree and changes xml. 
                // Since the template configurations are generated from package tree, they get 
                // component types from their package trees.
                // Here translate the component types for package tree components to changes xml types.
                switch (componentType)
                {
                    case "ConnectionManager":
                        componentType = "ConnectionManager";
                        break;
                    case "Executable":
                    case "Component":
                        componentType = "Component";
                        break;
                    case "Variable":
                        componentType = "Variable";
                        break;
                }

                // Create a changes xml document.
                XmlDocument changesXml = CreateChangesXmlInstance();
                XmlElement changesXmlRoot = (XmlElement)changesXml.FirstChild;

                // Create the changes xml component.
                XmlElement changesXmlComponent =
                    (XmlElement)AppendComponent(
                    changesXmlRoot, componentType, componentName, componentUri);

                AddScalarProperties(packageTreeComponent, templateComponent, changesXmlComponent,
                    componentName, componentType, componentUri, changesXmlRoot);

                AddMappings(packageTreeComponent, templateComponent, changesXmlComponent,
                    componentName, componentType, componentUri, changesXmlRoot);

                AddCollections(packageTreeComponent, templateComponent, changesXmlComponent);

                // Return the component.
                return changesXmlComponent;
            }
            catch
            {
                return null;
            }
        }

        private static void AddCollections(
            XmlNode packageTreeComponent, XmlNode templateComponent, XmlElement changesXmlComponent)
        {
            if (templateComponent.Attributes["ContainsCollections"].Value == "True")
            {
                // Locate the 'CollectionInformation' node in changes xml.
                XmlNode xCollections =
                    changesXmlComponent.SelectSingleNode("./CollectionInformation");

                // Add the CollectionInformation only if there is 
                // no earlier CollectionInformation defined.
                if (xCollections == null)
                {
                    // Get the package tree collections.
                    XmlNodeList templateTreeCollections = packageTreeComponent.SelectNodes(
                        "./Node[@Name=\"Inputs\" or @Name=\"Outputs\"]/Node[@Type=\"Input\" or @Type=\"Output\"]");

                    if (templateTreeCollections.Count > 0)
                    {
                        // Add the 'CollectionInformation' node.
                        XmlElement changesXmlCollectionInfo =
                            AppendChildElement(changesXmlComponent, "CollectionInformation", null);

                        foreach (XmlNode templateTreeCollection in templateTreeCollections)
                        {
                            // Add in the Collection node.
                            XmlElement changesXmlCollection =
                                AppendChildElement(changesXmlCollectionInfo, "Collection",
                                new string[]{
                                "Name",templateTreeCollection.Attributes["Name"].Value,
                                "Type",templateTreeCollection.Attributes["Type"].Value});

                            #region Add in collection properties.

                            XmlNodeList templateTreeCollectionProperties =
                                templateTreeCollection.SelectNodes("./Property");

                            if (templateTreeCollectionProperties.Count > 0)
                            {
                                XmlElement changesXmlCollectionProperties =
                                    AppendChildElement(changesXmlCollection,
                                    "Properties", null);

                                foreach (XmlNode packageCollectionProperty in
                                    templateTreeCollectionProperties)
                                    AppendChildElement(changesXmlCollectionProperties,
                                    "Property",
                                    new string[]{
                                    "Name",
                                    packageCollectionProperty.Attributes["Name"].Value,
                                    "Value",
                                    packageCollectionProperty.Attributes["Value"].Value});

                            }

                            #endregion

                            #region Add in the columns.

                            XmlNodeList templateTreeColumns = templateTreeCollection.SelectNodes(
                                "./Node[@Name=\"Columns\"]/Node[@Type=\"Column\"]");

                            if (templateTreeColumns.Count > 0)
                            {
                                XmlElement changesXmlColumns =
                                    AppendChildElement(
                                    changesXmlCollection, "Columns", null);

                                foreach (XmlNode templateTreeColumn in templateTreeColumns)
                                {
                                    XmlElement changesXmlColumn =
                                        AppendChildElement(
                                        changesXmlColumns, "Column",
                                        new string[] { 
                                        "Name", templateTreeColumn.Attributes["Name"].Value });

                                    // Add in the column properties.
                                    XmlNodeList templateTreeColumnProperties =
                                        templateTreeColumn.SelectNodes(
                                        "./Property");

                                    if (templateTreeColumnProperties.Count > 0)
                                    {
                                        XmlElement changesXmlColumnProperties =
                                            AppendChildElement(
                                            changesXmlColumn, "Properties", null);

                                        foreach (XmlNode packageColumnProperty
                                            in templateTreeColumnProperties)
                                            AppendChildElement(
                                                changesXmlColumnProperties,
                                                "Property",
                                                new string[]{
                                        "Name",
                                        packageColumnProperty.Attributes["Name"].Value,
                                        "Value",
                                        packageColumnProperty.Attributes["Value"].Value});
                                    }
                                }
                            }

                            #endregion
                        }
                    }
                }
            }
        }

        private static void AddMappings(
            XmlNode packageTreeComponent, XmlNode templateComponent, XmlNode changesXmlComponent,
            string componentName, string componentType,
            string componentUri, XmlElement changesXmlRoot)
        {
            // Fill in all the mappings.
            if (templateComponent.Attributes["ContainsMappings"].Value == "True")
            {
                // Locate the 'Mappings node in changes xml.
                XmlNode xMappings = changesXmlComponent.SelectSingleNode("./Mappings");

                // Add the mappings only if there is no earlier mapping defined.
                if (xMappings == null)
                    foreach (XmlNode xMapping in packageTreeComponent.SelectNodes("./Mapping"))
                    {
                        AppendMapping(changesXmlRoot, componentName, componentType, componentUri,
                            xMapping.Attributes["SourceColumn"].Value,
                            (xMapping.Attributes["Usage"].Value == "True") ? true : false,
                            xMapping.Attributes["DestinationColumn"].Value);
                    }
            }
        }

        private static void AddScalarProperties(
            XmlNode packageTreeComponent, XmlNode templateComponent, XmlNode changesXmlComponent,
            string componentName, string componentType,
            string componentUri, XmlElement changesXmlRoot)
        {
            // Create the component scalar properties.
            foreach (XmlNode templateScalarProperty in
                templateComponent.SelectNodes("./ConfigurableProperty"))
            {
                // Locate the 'Property' node in changes xml.
                XmlNode xProperty =
                    changesXmlComponent.SelectSingleNode(
                    "./Properties/Property[@Name=\"" +
                    templateScalarProperty.Attributes["Name"].Value + "\"]");

                // Add the Property only if there is no earlier Property defined.
                if (xProperty == null)
                {
                    // Locate the property in packageTree.
                    XmlNode packageTreeProperty =
                        packageTreeComponent.SelectSingleNode("./Property[@Name=\"" +
                        templateScalarProperty.Attributes["Name"].Value + "\"]");

                    // Add it to the changes xml.
                    if (packageTreeProperty != null)
                        AppendComponentProperty(
                            changesXmlRoot, componentName, componentType, componentUri,
                            packageTreeProperty.Attributes["Name"].Value,
                            packageTreeProperty.Attributes["Value"].Value);
                }
            }
        }

        internal static XmlDocument CreateChangesXmlInstance()
        {
            XmlDocument changesXml = new XmlDocument();
            XmlElement instanceRoot = changesXml.CreateElement("PackageChanges");
            changesXml.AppendChild(instanceRoot);
            return changesXml;
        }

        private static XmlNode AppendComponent(
            XmlElement xRootElement, string componentType,
            string componentName, string componentUri)
        {
            XmlNode instanceComponent;

            // Check if we already have this component into our instance data.
            // If not, add it.
            switch (componentType)
            {
                case "Component":
                case "Variable":
                case "ConnectionManager":
                    instanceComponent =
                        xRootElement.SelectSingleNode(
                        "//" + componentType +
                        "[@URL=\"" + componentUri + "\"]");

                    if (instanceComponent == null)
                        instanceComponent =
                            AppendChildElement(
                            xRootElement,
                            componentType,
                            new string[] { 
                                "Name",componentName,
                                "URL", componentUri
                            });
                    break;
                default:
                    return null;
            }

            return instanceComponent;
        }

        private static void AppendComponentProperty(XmlElement xRootElement,
            string componentName, string componentType, string componentUri,
            string scalarPropertyName, string scalarPropertyValue)
        {
            XmlNode instanceComponent =
                AppendComponent(xRootElement, componentType,
                componentName, componentUri);

            // Check if we have the 'Properties' node in instance data.
            XmlNode xProperties =
                instanceComponent.SelectSingleNode("./Properties");
            // If not, add it.
            if (xProperties == null)
                xProperties =
                    AppendChildElement(
                    instanceComponent,
                    "Properties", null);

            // Add in the 'Property' nodes.
            XmlNode instanceProperty =
                xProperties.SelectSingleNode(
                "./Property[@Name=\"" + scalarPropertyName + "\"]");

            // If not add it.
            if (instanceProperty == null)
                instanceProperty =
                    AppendChildElement(
                    xProperties,
                    "Property",
                    new string[] { 
                        "Name", scalarPropertyName, 
                        "Value", scalarPropertyValue });

            // If the property value is null, remove the property element.
            if (scalarPropertyValue == null)
            {
                instanceProperty.ParentNode.RemoveChild(instanceProperty);

                // if the component node does not have any child, remove it.
                if (instanceComponent.ChildNodes.Count == 0)
                    instanceComponent.ParentNode.
                        RemoveChild(instanceComponent);
            }
            else
                // Update the property value.
                instanceProperty.Attributes["Value"].Value =
                    scalarPropertyValue;
        }

        private static void AppendMapping(XmlElement xRootElement,
            string componentName, string componentType, string componentUri,
            string sourceColumnName, bool sourceColumnUsage,
            string destinationColumnName)
        {
            XmlNode instanceComponent =
                AppendComponent(xRootElement, componentType,
                componentName, componentUri);

            // Check if we have the 'Mappings' node in instance data.
            XmlNode xMappings =
                instanceComponent.SelectSingleNode("./Mappings");
            // If not, add it.
            if (xMappings == null)
                xMappings =
                    AppendChildElement(
                    instanceComponent,
                    "Mappings", null);

            // Check if we have the changed mapping in instance data.
            // Assumption: Taking the source column name as the key for search.
            XmlNode instanceMapping = xMappings.SelectSingleNode(
                "./Mapping[@SourceColumn=\"" +
                sourceColumnName + "\"]");

            // If not add it.
            if (instanceMapping == null)
                instanceMapping =
                    AppendChildElement(
                    xMappings,
                    "Mapping",
                    new string[] { 
                        "Usage", sourceColumnUsage ? "True" : "False",
                        "SourceColumn", sourceColumnName,
                        "DestinationColumn", destinationColumnName
                    });
        }

        private static XmlNode AppendCollection(XmlElement xRootElement,
            string componentName, string componentType, string componentUri,
            string columnCollectionType, string columnCollectionName)
        {
            // Locate the configurable component.
            XmlNode xnodeConfigurableComponent =
                AppendComponent(xRootElement, componentType,
                componentName, componentUri);

            // Locate the 'CollectionInformation' element.
            XmlNode xCollectionInformation =
                xnodeConfigurableComponent.SelectSingleNode(
                "./CollectionInformation");

            if (xCollectionInformation == null)
                xCollectionInformation =
                    AppendChildElement(
                    xnodeConfigurableComponent, "CollectionInformation", null);

            // Locate the 'Collection' element.
            XmlNode xnodeCollection =
                xCollectionInformation.SelectSingleNode(
                "./Collection[@Type=\"" + columnCollectionType +
                "\" and @Name=\"" + columnCollectionName + "\"]");

            // Create a 'Collection' element.
            if (xnodeCollection == null)
                xnodeCollection =
                    AppendChildElement(
                    xCollectionInformation,
                    "Collection",
                    new string[] {
                        "Name", columnCollectionName,
                        "Type", columnCollectionType
                    });
            return xnodeCollection;
        }

        private static void AppendColumnCollectionProperty(
            XmlElement xRootElement, string componentName, string componentType,
            string componentUri, string columnCollectionType,
            string columnCollectionName, string scalarPropertyName,
            string scalarPropertyValue)
        {
            XmlNode xnodeCollection =
                AppendCollection(xRootElement,
                componentName, componentType, componentUri,
                columnCollectionType, columnCollectionName);

            // Check if we have the 'Properties' node in instance data.
            XmlNode xProperties =
                xnodeCollection.SelectSingleNode("./Properties");
            // If not, add it.
            if (xProperties == null)
                xProperties =
                    AppendChildElement(
                    xnodeCollection,
                    "Properties", null);

            // Add in the 'Property' nodes.
            XmlNode instanceProperty =
                xProperties.SelectSingleNode(
                "./Property[@Name=\"" + scalarPropertyName + "\"]");

            // If not add it.
            if (instanceProperty == null)
                AppendChildElement(
                xProperties,
                "Property",
                new string[] { 
                        "Name", scalarPropertyName,
                        "Value", scalarPropertyValue });
        }

        private static void AppendColumnProperty(
            XmlElement xRootElement, string componentName, string componentType,
            string componentUri, string columnCollectionType,
            string columnCollectionName, string columnName,
            string scalarPropertyName, string scalarPropertyValue)
        {
            XmlNode xnodeCollection =
                AppendCollection(xRootElement,
                componentName, componentType, componentUri,
                columnCollectionType, columnCollectionName);

            // Check if we have the 'Columns' node in instance data.
            XmlNode xColumns =
                xnodeCollection.SelectSingleNode("./Columns");
            // If not, add it.
            if (xColumns == null)
                xColumns =
                    AppendChildElement(
                    xnodeCollection,
                    "Columns", null);

            // Locate the 'Column' element.
            XmlNode xColumn =
                xColumns.SelectSingleNode(
                "./Column[@Name=\"" + columnName + "\"]");

            // Create a 'Column' element, if not found.
            if (xColumn == null)
                xColumn =
                    AppendChildElement(
                    xColumns,
                    "Column",
                    new string[] { 
                    "Name", columnName
                });

            // Check if we have the 'Properties' node in instance data.
            XmlNode xProperties =
                xColumn.SelectSingleNode("./Properties");

            // If not, add it.
            if (xProperties == null)
                xProperties =
                    AppendChildElement(
                    xColumn,
                    "Properties", null);

            // Add in the 'Property' nodes.
            XmlNode instanceProperty =
                xProperties.SelectSingleNode(
                "./Property[@Name=\"" + scalarPropertyName + "\"]");

            // If not add it.
            if (instanceProperty == null)
                AppendChildElement(
                xProperties,
                "Property",
                new string[] { 
                        "Name", scalarPropertyName,
                        "Value", scalarPropertyValue });
        }

        #endregion

        #region General xml document management methods.

        internal static XmlElement AppendChildElement(XmlNode parentElement,
            string childelementName, string[] attributeFollowedByValue)
        {
            return AppendChildElement(
                parentElement, childelementName,
                null, attributeFollowedByValue);
        }

        internal static XmlElement AppendChildElement(XmlNode parentElement,
            string childelementName, string innerXml,
            string[] attributeFollowedByValue)
        {
            XmlElement childElement;
            if (parentElement is XmlDocument)
                // Special treatment for XmlDocument.
                childElement =
                    ((XmlDocument)parentElement).
                    CreateElement(childelementName);
            else
                childElement =
                    parentElement.OwnerDocument.
                    CreateElement(childelementName);

            parentElement.AppendChild(childElement);

            if (!string.IsNullOrEmpty(innerXml))
                childElement.InnerXml = innerXml;

            AppendAttribute(childElement, attributeFollowedByValue);

            return childElement;
        }

        internal static void AppendAttribute(XmlNode parentElement,
            string[] attributeFollowedByValue)
        {
            if (attributeFollowedByValue == null)
                return;

            for (int i = 0; i < attributeFollowedByValue.Length; i++)
            {
                XmlAttribute attrib =
                    parentElement.OwnerDocument.
                    CreateAttribute(attributeFollowedByValue[i]);
                parentElement.Attributes.Append(attrib);

                i++;

                attrib.Value =
                    (i >= attributeFollowedByValue.Length) ?
                    string.Empty : attributeFollowedByValue[i];
            }
        }

        #endregion

        #endregion
    }
}
